package com.example.bbgds.test;

import java.util.ArrayList;

import android.content.Context;
import android.support.v4.view.ViewConfigurationCompat;
import android.util.Log;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;

/**
 * Created by bbgds on 2016/3/1.
 */
public class EventController {
    private static final int MIN_FLING_VELOCITY = 1500;
    private static final int MIN_FLING_INSTANCE = 80;

    private ArrayList<ExpandView> mDropTargets = new ArrayList<ExpandView>();
    private VelocityTracker mVelocityTracker;

    private float mLastMotionX;
    private float mLastMotionY;
    private float mStartMotionX;
    private float mStartMotionY;
    private ExpandView mDropTarget = null;
    private Context mContext = null;
    private float mMinVelocity;
    private float mMinFlipDistance;
    private float mMinMoveDistance;
    private View mParentLayout;
    private int mTouchSlop;
    private int mTouchSlopSquared;
    private GestureDetector mGestureDetector;
    private EventCallback mEventCallback;

    public EventController(View parent, EventCallback callback) {
        mContext = parent.getContext();
        mParentLayout = parent;
        mEventCallback = callback;
        ViewConfiguration vc = ViewConfiguration.get(mContext);
        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(vc);
        mTouchSlopSquared = mTouchSlop * mTouchSlop;
        final float density = mContext.getResources().getDisplayMetrics().density;
        mMinVelocity = MIN_FLING_VELOCITY * density;
        mMinFlipDistance = MIN_FLING_INSTANCE * density;
        mMinMoveDistance = mContext.getResources().getDisplayMetrics().widthPixels / 2;
        mGestureDetector = new GestureDetector(mContext, new TouchDetector(mContext));
    }

    private void expandTarget(int gravity) {
        for (ExpandView target : mDropTargets) {
            if (target.getGravity() == gravity) {
                target.expand(true);
                mDropTarget = null;
            }
        }
    }

    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ignoreTouchEvent()) {
            return false;
        }
        mGestureDetector.onTouchEvent(ev);
        acquireVelocityTrackerAndAddMovement(ev);
        final int action = ev.getAction();
        Log.e("zhu","action=" + action);
        switch (action) {
            case MotionEvent.ACTION_MOVE:
                float deltaY = ev.getRawY() - mLastMotionY;
                float deltaX = ev.getRawX() - mLastMotionX;
                float x = ev.getX();
                float y = ev.getY();
                if (deltaY != 0 && canScroll(mParentLayout, true, deltaY, x, y)) {
                    return false;
                }
                mLastMotionX = ev.getRawX();
                mLastMotionY = ev.getRawY();
                if (mDropTarget != null) {
                    mDropTarget.dragTo(deltaX, deltaY, false);
                    return true;
                } else {
                    mDropTarget = findDropTarget(deltaX, deltaY);
                    if (mDropTarget != null) {
                        mDropTarget.dragTo(deltaX, deltaY, false);
                        return true;
                    }
                }
                break;
            case MotionEvent.ACTION_DOWN:
                mLastMotionX = (int) ev.getRawX();
                mLastMotionY = (int) ev.getRawY();
                break;
        }
        return false;
    }

    public boolean onTouchEvent(MotionEvent ev) {
        if (ignoreTouchEvent()) {
            return false;
        }
        mGestureDetector.onTouchEvent(ev);
        acquireVelocityTrackerAndAddMovement(ev);
        final int action = ev.getAction();
        Log.e("zhu","action=" + action);
        switch (action) {
            case MotionEvent.ACTION_MOVE:
                float deltaY = ev.getRawY() - mLastMotionY;
                float deltaX = ev.getRawX() - mLastMotionX;
                mLastMotionX = ev.getRawX();
                mLastMotionY = ev.getRawY();
                if (mDropTarget != null) {
                    mDropTarget.dragTo(deltaX, deltaY, false);
                    return true;
                } else {
                    mDropTarget = findDropTarget(deltaX, deltaY);
                    if (mDropTarget != null) {
                        mDropTarget.dragTo(deltaX, deltaY, false);
                        return true;
                    }
                }
                break;
            case MotionEvent.ACTION_DOWN:
                mLastMotionX = (int) ev.getRawX();
                mLastMotionY = (int) ev.getRawY();
                mStartMotionX = mLastMotionX;
                mStartMotionY = mLastMotionY;
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mLastMotionX = (int) ev.getRawX();
                mLastMotionY = (int) ev.getRawY();
                cancelTouch();
                break;
        }
        return false;
    }

    public void addDropTarget(ExpandView target) {
        mDropTargets.add(target);
    }

    public void removeDropTarget(ExpandView target) {
        mDropTargets.remove(target);
    }

    private ExpandView findDropTarget(float deltaX, float deltaY) {
        if ((Math.abs(deltaY) > Math.abs(deltaX) && Math.abs(deltaY) > 1)) {
            if (deltaY > 0) {
                return findDropTarget(Gravity.TOP);
            } else if (deltaY < 0) {
                return findDropTarget(Gravity.BOTTOM);
            }
        } else if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 1) {
            if (deltaX > 0) {
                return findDropTarget(Gravity.LEFT);
            } else if (deltaX < 0) {
                return findDropTarget(Gravity.RIGHT);
            }
        }
        return null;
    }

    private ExpandView findDropTarget(int gravity) {
        for (ExpandView target : mDropTargets) {
            if (target.getGravity() == gravity) {
                return target;
            }
        }
        return null;
    }

    private boolean ignoreTouchEvent() {
        for (ExpandView target : mDropTargets) {
            if (!target.isTranslationFinished() || target.isExpand()) {
                return true;
            }
        }
        return false;
    }

    private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(ev);
    }

    private void releaseVelocityTracker() {
        if (mVelocityTracker != null) {
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
    }

    private void cancelTouch() {
        float xvel = 0f;
        float yvel = 0f;
        if (mVelocityTracker != null) {
            ViewConfiguration config = ViewConfiguration.get(mContext);
            mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
            xvel = mVelocityTracker.getXVelocity();
            yvel = mVelocityTracker.getYVelocity();
        }
        if (mDropTarget != null) {
            mDropTarget.releaseViewForPointerUp(xvel, yvel, false);
        } else {
            float dx = mLastMotionX - mStartMotionX;
            float dy = mLastMotionY - mStartMotionY;
            if (Math.abs(xvel) > Math.abs(yvel)
                    && ((Math.abs(dx) > mMinFlipDistance && Math.abs(xvel) >= mMinVelocity) || Math.abs(dx) > mMinMoveDistance)) {
                if (xvel > 0 && dx > 0) { // left to right
                    expandTarget(Gravity.LEFT);
                } else if (xvel < 0 && dx < 0) { // right to left
                    expandTarget(Gravity.RIGHT);
                }
            } else if (Math.abs(yvel) > Math.abs(xvel)
                    && ((Math.abs(dy) > mMinFlipDistance && Math.abs(yvel) >= mMinVelocity) || Math.abs(dy) > mMinMoveDistance)) {
                if (yvel > 0 && dy > 0) { // top to bottom
                    expandTarget(Gravity.TOP);
                } else if (yvel < 0 && dy < 0) { // right to left
                    expandTarget(Gravity.BOTTOM);
                }
            }
        }
        mDropTarget = null;
        mLastMotionX = 0;
        mLastMotionY = 0;
        mStartMotionY = 0;
        mStartMotionX = 0;
        releaseVelocityTracker();
    }

    public static boolean canScroll(View v, boolean checkV, float dx, float x, float y) {
        if (v instanceof ViewGroup) {
            final ViewGroup group = (ViewGroup) v;
            final int scrollX = v.getScrollX();
            final int scrollY = v.getScrollY();
            final int count = group.getChildCount();
            for (int i = count - 1; i >= 0; i--) {
                final View child = group.getChildAt(i);
                if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && y + scrollY >= child.getTop()
                        && y + scrollY < child.getBottom()
                        && canScroll(child, true, dx, x + scrollX - child.getLeft(), y + scrollY - child.getTop())) {
                    return true;
                }
            }
        }

        return checkV && v.canScrollVertically((int) -dx);
    }

    private class TouchDetector extends GestureDetector.SimpleOnGestureListener {
        private static final int SWIPE_MAX_DEVIATION = 250;
        private int swipeMinDistance;
        private int swipeMinVelocity;

        public TouchDetector(Context context) {
            final ViewConfiguration vc = ViewConfiguration.get(context);
            swipeMinDistance = vc.getScaledTouchSlop();
            swipeMinVelocity = vc.getScaledMinimumFlingVelocity();
        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            return false;
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            if (e1 == null || e2 == null) {
                return false;
            }
            if (Math.abs(e1.getX() - e2.getX()) > SWIPE_MAX_DEVIATION) {
                return false;
            }
            if (e1.getY() - e2.getY() > swipeMinDistance && Math.abs(velocityY) > swipeMinVelocity) {
                if(mEventCallback != null){
                    mEventCallback.flingUp();
                    return true;
                }
            } else if (e2.getY() - e1.getY() > swipeMinDistance && Math.abs(velocityY) > swipeMinVelocity) {
                if(mEventCallback != null){
                    mEventCallback.flingDown();
                    return true;
                }
            }
            return false;
        }

    }

    public interface EventCallback{
        public abstract void flingUp();
        public abstract void flingDown();
    }
}
